#ifndef LOWLEVEL_H
#define LOWLEVEL_H

#include <Windows.h>
#include <dsound.h>
#include <vector>
#include <deque>
#include <map>
#include <cmath>
#include "WaveFile.h"

#undef PlaySound

namespace sndcore
{

#pragma region MACROS
	#define PI 3.14159265f
	typedef SHORT far *LPSHORT;
	#define BYTE_RANGE	128
	#define WORD_RANGE  32768
	#define SAMPLE16TO8(a) ((BYTE)((a+WORD_RANGE)>>8))
	#define SAMPLE8TO16(a) ((short)(a<<8)-WORD_RANGE)
	#define STREAM_BUFFER_MILLISECONDS 1000
	#define STREAM_FILEBUF_SIZE        32768
#pragma endregion

#pragma region ENUMERATIONS
	enum SNDLRPOPTIONS { SLO_MONO, SLO_STEREO, SLO_COPY, SLO_COMBINE };
#pragma endregion

#pragma region SATURATE
template <typename T>
T Saturate(T val);
#pragma endregion

#pragma region SOUNDBUFFERS
	enum SB_CREATE_FLAGS {SCF_PRIMARY = 1}; 
	enum SB_PLAY_FLAGS	 {SPF_LOOP = 1};


	struct SoundBufferOptions
	//-------------------------------------------------------------------------------
	/*!
	\struct SoundBufferOptions
	\brief  Options for initializing sound buffers.
	\author	Ben Smith
	*/
	//-------------------------------------------------------------------------------
	{
		DWORD			size_;
		SB_CREATE_FLAGS	flags_;
		WAVEFORMATEX	format_;
		unsigned		songlen_;
	};

	class ISoundBuffer
	//-------------------------------------------------------------------------------
	/*!
	\class ISoundBuffer
	\brief The abstracted sound buffer interface.  Describes buffer classes which can
			be read from, written to, played and stopped.
	\author	Ben Smith
	*/
	//-------------------------------------------------------------------------------
	{
	public:
		ISoundBuffer() {}
		virtual ~ISoundBuffer() {}
	public:

		//Buffer I/O
		virtual bool WriteBuffer(DWORD size, LPVOID data, DWORD offset ) = 0;
		virtual bool ReadBuffer(DWORD offset, DWORD size, LPVOID *data) const = 0;

		//Buffer Functions
		virtual bool Play(SB_PLAY_FLAGS flags) = 0;
		virtual bool Stop() = 0;

		//Buffer Initialization
		virtual bool Initialize(const SoundBufferOptions& buffOptions) = 0;
		virtual bool Shutdown() = 0;

		//Buffer Get/Set
		virtual  const WAVEFORMATEX&  GetFormat() const=0;
		virtual DWORD GetSize() const=0;

		//virtual void Reset();
	};

	class SoundBufferDS : public ISoundBuffer
	//-------------------------------------------------------------------------------
	/*!
	\class SoundBufferDS
	\brief A DS buffer wraps a DirectSound buffer.  The buffer can be read, written to,
			and played.
	\author	Ben Smith
	*/
	//-------------------------------------------------------------------------------
	{
	private:
		LPDIRECTSOUNDBUFFER	pBuffer_;
		WAVEFORMATEX        format_;
	public:
		SoundBufferDS(LPDIRECTSOUNDBUFFER pBuff, WAVEFORMATEX& format) : pBuffer_(pBuff), format_(format) {}
		virtual ~SoundBufferDS() { Shutdown(); }
	public:
		HRESULT GetCursorPosition(DWORD *playCursor, DWORD *writeCursor) const;	//--
		bool SetCursorPosition(DWORD playCursor);								//--
		virtual bool WriteBuffer(DWORD size, LPVOID data, DWORD offset = 0 );								//--
		virtual bool ReadBuffer(DWORD offset, DWORD size, LPVOID *data) const;	        //--
		virtual bool Play(SB_PLAY_FLAGS flags);											//--
		virtual bool Stop();															//--
		virtual bool Initialize(const SoundBufferOptions& buffOptions);					//--
		virtual bool Shutdown();														//--
		virtual const WAVEFORMATEX& GetFormat() const { return format_; }				//--
		virtual DWORD GetSize() const;													//--
	};

	class SoundBufferUser : public ISoundBuffer
	//-------------------------------------------------------------------------------
	/*!
	\class SoundBufferUser
	\brief A user buffer is a wrapper around allocated buffer data.  The data can be 
			read and written to.  The play command does nothing.
	\author	Ben Smith
	*/
	//-------------------------------------------------------------------------------
	{
		friend class Channel;
		friend class LowLevelCore;
	private:
		LPVOID				buffer_;
		SoundBufferOptions  options_;
		HANDLE              m_hStreamFile;
		OVERLAPPED          m_streamFileStatus;
		BYTE*               m_streamFileBuf;
		DWORD               mlFileTotalByteRead;
		DWORD               mlFileReadCursor;
		DWORD				mlBeginOffset;
		unsigned			mStreamBuffSize;
	public:
		SoundBufferUser() {}
		SoundBufferUser(const SoundBufferOptions& options, int buff_size = 0) : buffer_(NULL), m_streamFileBuf( NULL ) 
		{ 
			mStreamBuffSize = buff_size;
			if( buff_size > 0 ) 
				m_streamFileBuf = new BYTE[ mStreamBuffSize ];
			Initialize(options); 
		}
		virtual ~SoundBufferUser() { if( m_streamFileBuf ) { delete []m_streamFileBuf; m_streamFileBuf = 0; } Shutdown(); }
	public:
		virtual bool WriteBuffer(DWORD size, LPVOID data, DWORD offset = 0 ); 			//--
		virtual bool ReadBuffer(DWORD offset, DWORD size, LPVOID *data) const;			//--
		virtual bool Play(SB_PLAY_FLAGS /*flags*/) { return false; }					//--
		virtual bool Stop() { return false; }											//--
		virtual bool Initialize(const SoundBufferOptions& buffOptions);					//--
		virtual bool Shutdown();														//--
		virtual  const WAVEFORMATEX&  GetFormat() const {return options_.format_;}		//--
		virtual DWORD GetSize() const { return options_.size_; }						//--

		LPVOID DirectRead(DWORD* size=NULL);
		SoundBufferOptions GetOptions() const { return options_; }	
	};
#pragma endregion

#pragma region SOUND
class Sound
//-------------------------------------------------------------------------------
/*!
\class Sound
\brief A sound is an allocation of PCM sample data.  Sounds are owned by the
		LowLevelCore.
\author	Ben Smith
*/
//-------------------------------------------------------------------------------
{
	friend bool LowLevelDownsample(const Sound& srcSound, Sound& dstSound, const WAVEFORMATEX* srcFormat, const WAVEFORMATEX* dstFormat);
	friend bool LowLevelResampleLerp(const Sound& srcSound, Sound& dstSound, DWORD srcIndexA, DWORD srcIndexB, DWORD dstIndexA, DWORD dstIndexB, SNDLRPOPTIONS options);
	
public:
	Sound(SoundBufferUser* buffer, bool stream, unsigned id ) : buffer_(buffer), stream_( stream ), id_(id) {}
	~Sound() { if (buffer_) delete buffer_; }

	bool GetStream() const { return stream_; }
	unsigned GetId() const { return id_; }
	const WAVEFORMATEX& GetFormat() const { return buffer_->GetFormat(); }
	DWORD GetSize() const { return buffer_->GetSize(); }
	SoundBufferUser*	buffer_;
	std::wstring mStrPath;
	float mSavePitchSteps;
	WAVEFORMATEX mPrimaryFormat;
	DWORD mStreamTotalDataSize;

	int GetSongLength() const;
	int GetBitsPerSample() const;
	int GetNumChannels() const;
	int GetFrequency() const;

private:
	bool				stream_;
	unsigned			id_;
};
#pragma endregion

#pragma region CHANNEL
struct ReadCursor
//-------------------------------------------------------------------------------
/*!
\struct ReadCursor
\brief  A read cursor is an offset into a sound buffer, a local gain scalar,
		and a delay value.  Read cursors can be delayed for use with delay DSP
		effects.
\author	Ben Smith
*/
//-------------------------------------------------------------------------------
{
	DWORD	pos_;
	float   gain_;
	bool    played_;
	DWORD	delay_;

	ReadCursor() : pos_(0), gain_(1.f), played_(false), delay_(0) {}
	ReadCursor(DWORD pos, DWORD delay, float gain) : pos_(pos), gain_(gain),
		played_(false), delay_(delay) {}

	void DecDelay() { delay_ = delay_ ? delay_ - 1 : 0; }
	bool Played() const { return played_;  }
	void SetPlayed(bool played) { played_ = played; }
	bool Playing() const { return !played_ && delay_ == 0; }
	bool Delayed() const { return delay_!=0; }
};
typedef std::vector<ReadCursor> readcursorlist_t;

class Channel
//-------------------------------------------------------------------------------
/*!
\class Channel
\brief A channel is an instance of a sound.  Channels can be played, paused, and
		stopped.  Their volumes, pan, gain, and DSP effect powers can all be
		modified on the fly.  They can be played to loop or simply as one-shots.
		Note: When a channel has finished playing as a one-shot sound, it will 
		automatically be deleted from memory, and thus its memory invalidated.
		Users should use query functions from the LowLevelCore to find out whether
		a channel still exists as opposed to owning a pointer to the channel itself.
\author	Ben Smith
*/
//-------------------------------------------------------------------------------
{
	friend class LowLevelCore;
	friend class Sound;
	friend class MiniMixer;
private:
	Sound* pSound_;
	LowLevelCore * pLowLevelCore_;
	readcursorlist_t  playCursors_;
	DWORD  writeCursor_;
	float  gain_;	//0 -> 1 (quiet to loud)
	float  pan_;	//0 = LCHAN, 1 = RCHAN, 0.5 = MID
	bool   played_;
	bool   looping_;
	bool   paused_;
	bool   eof_reached_;
	float  distortionPower_;
	std::deque< Sound * > StreamBuffers;
private:
	bool SoundIs(unsigned soundId) { return pSound_->GetId() == soundId; }
	Sound * GetSound() const { return pSound_; }
	void MoveCursor( unsigned i, DWORD move_pos );
	void SetCursor( const int index, float percentage );
	void AsyncUpdate( );
public:                                                        //TODO: add offset
	Channel( ) : pSound_( NULL ), writeCursor_( 200 ), gain_( 1.0f ), pan_( 0.5f ), played_(false),
				 looping_(false), paused_(false), distortionPower_(0.f),  eof_reached_( false )  { playCursors_.push_back(ReadCursor()); }
	Channel( Sound* sound, LowLevelCore * manager ) : pSound_( sound ), pLowLevelCore_( manager ),
		                          gain_( 1.0f ), pan_( 0.5f ), played_(false), looping_(false),
								  paused_(false), distortionPower_(0.f), eof_reached_( false ) { playCursors_.push_back(ReadCursor()); }
	~Channel( ) {}
public:
	void Resume() { paused_ = false; } 
	void Pause() { paused_ = true; }
	bool Paused() const { return paused_; }
	bool HasPlayed() const { return playCursors_.back().Played(); } 
	void SetPan(float pan) { pan_ = Saturate<float>(pan); }
	float GetPan() const { return pan_; }
	void SetGain(float gain) { gain_ = Saturate<float>(gain); }
	float GetGain() const { return gain_; }
	void SetLooping(bool looping) { looping_ = looping; }
	bool GetLooping() const { return looping_; }
	DWORD GetNumTaps() const { return (DWORD)playCursors_.size(); }
	void RemoveDelayFilters() { while(playCursors_.size() > 1) playCursors_.pop_back(); }
	void RemoveDistortionFilters() { distortionPower_=0.f; }
	float GetDelayDSPPower() const { if (HasDelayDSP())return playCursors_[1].gain_; return 0.f; }
	float GetCompressionDSPPower() const { return distortionPower_; }
	void SetDelayDSPPower(float power) { if (HasDelayDSP()) playCursors_[1].gain_=power;  }
	void SetCompressionDSPPower(float power) { distortionPower_=power; }
	void AddReverbDSP(float power);
	void AddDistortionDSP(float power) { SetCompressionDSPPower(power); }
	bool HasDelayDSP() const { return playCursors_.size() != 1; }
	bool HasCompressionDSP() const { return distortionPower_ != 0; }

 
	int GetSongLength() const;
	int GetBitsPerSample() const;
	int GetNumChannels() const;
	int GetFrequency() const;

	void KillChannelTest( const int play_index, const int data_size );
private:
	template< typename T >
	void MoveCursorsByTemplate()
	//-------------------------------------------------------------------------------
	/*!
	\brief Moves all ReadCursors in a channel forward one step.
	\author	Ben Smith
	*/
	//-------------------------------------------------------------------------------
	{
		for (unsigned i=0; i < playCursors_.size(); ++i) 
		{
			//move the play cursor buffer position by 1
			MoveCursor(i,1);
			KillChannelTest( i , sizeof( T ) );
		}
	}

	template< typename T >
	bool GetDataFromOffset( int offset, unsigned i, T& val )
	//-------------------------------------------------------------------------------
	/*!
	\brief Returns templated sample data from the channel. The user can specify the
			index of the read cursor to read from and a offset (in samples) from that
			cursor.
	\param offset
			Number of samples to offset the read by.
	\param i
			Index of ReadCursor (in read cursor array) to read from.
	\param val
			Reference to sample value which will be written to upon function success.
	\return
			True if the read cursor specified can be read from (not delayed or finished
			playing), False otherwise.
	\author	Ben Smith
	*/
	//-------------------------------------------------------------------------------
	{
		if (!playCursors_[i].Playing())
			return false;

		int test_player_cursor = (playCursors_[i].pos_ + offset ) % ( pSound_->buffer_->GetSize()/sizeof(T) );

		if( pSound_->GetStream() && !StreamBuffers.empty() )
			//get short data from buffer
			val =  (T) (( ( T * )( (*StreamBuffers.begin())->buffer_->DirectRead( NULL ) ) )[test_player_cursor]*playCursors_[i].gain_) ;

		else 
			//get data from buffer
			val = (T)(( ( T * )( pSound_->buffer_->DirectRead( NULL ) ) )[test_player_cursor]*playCursors_[i].gain_ );
		return true;
	}

	template< typename T >
	void SetDataFromOffset( int offset, T & set_value )
	//-------------------------------------------------------------------------------
	/*!
	\brief Writes data into the sound buffer from the first read cursor.  The position
			of which to write to can be offset by a number of samples.
	\param offset
			Number of samples to offset the write by.
	\param set_value
			Value to write into buffer data.
	\author	Ben Smith
	*/
	//-------------------------------------------------------------------------------
	{
		int test_player_cursor = (playCursors_.front().pos_ + offset ) % ( pSound_->buffer_->GetSize()/sizeof(T) );


		if( pSound_->GetStream() && !StreamBuffers.empty() )
			//get short data from buffer
			( ( T * )( (*StreamBuffers.begin())->buffer_->DirectRead( NULL ) ) )[test_player_cursor] = set_value;

		else
		//get data from buffer
			( ( T * )( pSound_->buffer_->DirectRead( NULL ) ) )[test_player_cursor] = set_value;
	}

	
};
#pragma endregion

#pragma region MIXER
class MiniMixer
//-------------------------------------------------------------------------------
/*!
\class MiniMixer
\brief Owns a small buffer which sounds are mixed into before they are placed
		into the primary buffer.
\author	Ben Smith
*/
//-------------------------------------------------------------------------------
{
private:
	SoundBufferUser *mixed_;
public:
	MiniMixer(){}
	MiniMixer( const SoundBufferDS * primaryBuff, int size ) { Initialize( primaryBuff, size ); }
	~MiniMixer() { Shutdown(); }
public:
	bool Initialize( const SoundBufferDS * primaryBuff, int size );
	void Shutdown();
public: 
	void Reset( BYTE set_value );
	bool MixIn( Channel *mixin, DWORD size_, const float &mastaVol );
	SoundBufferUser *GetBuffer() const { return mixed_; } 
};
#pragma endregion

#pragma region SOUND MANAGER

typedef std::map< unsigned, Channel * > CHANNEL_MAP;
typedef CHANNEL_MAP::iterator CHANNEL_MAP_IT;

typedef std::map< unsigned, Sound* > SOUND_MAP;
typedef SOUND_MAP::iterator SOUND_MAP_IT;

class LowLevelCore
//-------------------------------------------------------------------------------
/*!
\class LowLevelCore
\brief The low level sound manager.  Used to store sounds, channels, and to
		wrap the DirectSound device.
\author	Ben Smith
*/
//-------------------------------------------------------------------------------
{
	friend class Channel;
private:
	CHANNEL_MAP			mChannels;
	SOUND_MAP			mSounds;
	SoundBufferDS *		mPrimaryBuffer;
	MiniMixer *			mMixer;
	LPDIRECTSOUND8		mDSoundInterface;
	int					mWriteCursor;
	unsigned			mWritePerFrame;
	float				mVolume;
	unsigned			mChannelId;
	unsigned			mSoundId;
private:
	bool		CreatePrimaryBuffer( bool for_tool );
public:
	void		Update( );
	bool		Initialize( HWND wind, bool for_tool );
	void		Shutdown( );
	float		GetVolume( ) const			{ return mVolume; }
	void		SetVolume( float val )		{ mVolume = Saturate<float>( val );  }
	bool		FileExists(const WCHAR* file, unsigned& soundID);

public:
	bool		LoadSound( const WCHAR * wav_path, unsigned& soundId, float pitchSteps );
	bool		LoadStream( const WCHAR * wav_path, unsigned& soundId, float pitchSteps );

	Sound*		GetSound( unsigned soundId );
	Channel*	PlaySound( unsigned soundId , unsigned* channelId=NULL );
	Channel*	GetChannel( unsigned channelId );

	void		StopChannel(unsigned channelId);
	void		ReleaseSound(unsigned soundId);

public: 
	LowLevelCore( HWND wind, int write_per_frame, bool for_tool=false ) : mVolume( 1.0f ), mChannelId(0), mSoundId(0), mPrimaryBuffer(0) { mWritePerFrame = write_per_frame; Initialize( wind, for_tool ); }
	~LowLevelCore() { Shutdown(); }

	
};
#pragma endregion

#pragma region HELPER FUNCTIONS
short Distortion(short sample, short threshold, BYTE bitsPerSample);
short MixSamples( short leftval, short rightval, float left_gain, float masterVol, int bitsPerSample  );
bool Pan(short& lChannel, short& rChannel, float panVal, BYTE bitsPerSample);
bool LowLevelResample(const Sound& srcSound, const WAVEFORMATEX* pDstFormat, float pitchSteps, Sound** dstSound);
bool LowLevelResampleLerp(const Sound& srcSound, Sound& dstSound, DWORD srcIndexA, DWORD srcIndexB, DWORD dstIndexA, DWORD dstIndexB, SNDLRPOPTIONS options);
bool LowLevelDownsample(const Sound& srcSound, Sound& dstSound, const WAVEFORMATEX& srcFormat, const WAVEFORMATEX& dstFormat);
bool LowLevelUpsample(const Sound& srcSound, Sound& dstSound, const WAVEFORMATEX& srcFormat, const WAVEFORMATEX& dstFormat);

#pragma endregion

}

template<typename T>
T sndcore::Saturate(T val)
//-------------------------------------------------------------------------------
/*!
\brief	Templated function to clamp a variable between 0 and 1
\param val
	Value to clamp
\return Clamped value
\author	Ben Smith
*/
//-------------------------------------------------------------------------------
{
	return val > 1.0f ? 1.0f : val < 0.0f ? 0.0f : val;
}

#endif
